#include "server_config.h"
#include <string.h>
#include <glib.h>
#include "gam_listener.h"
#include "gam_subscription.h"
#include "gam_server.h"
#include "gam_error.h"
#include "gam_pidname.h"
#ifdef ENABLE_INOTIFY
#include "gam_inotify.h"
#endif

/* private struct representing a single listener */
struct _GamListener {
  void *service;
  int pid;
  char *pidname;
  GList *subs;
};

/**
   @defgroup GamListener GamListener
   @ingroup Daemon
   @brief GamListener API.

   @{
*/

/**
   gam_listener_new:

   @service: service structure used to communicate with #GamListener
   @pid: the unique ID for this listener

   Creates a #GamListener

   Returns a new #GamListener on success, NULL otherwise
*/
GamListener *
gam_listener_new( void *service, int pid ) {
  GamListener *listener;
  g_assert( service );
  g_assert( pid != 0 );
  listener = g_new0( GamListener, 1 );
  listener->service = service;
  listener->pid = pid;
  listener->pidname = gam_get_pidname( pid );
  listener->subs = NULL;
  #ifdef GAM_LISTENER_VERBOSE
  GAM_DEBUG( DEBUG_INFO, "Created listener for %d\n", pid );
  #endif
  return listener;
}

/**
   gam_listener_free_subscription:

   @listener: the #GamListener
   @sub: the subscription to remove

   Frees a listener's subscription
*/
static void
gam_listener_free_subscription( GamListener *listener,
                                GamSubscription *sub ) {
  char *path;
  g_assert( listener );
  g_assert( sub );
  g_assert( g_list_find( listener->subs, sub ) );
  path = g_strdup( gam_subscription_get_path( sub ) );
  gam_remove_subscription( sub );
  #ifdef ENABLE_INOTIFY
  if( gam_inotify_is_running() && ( !gam_exclude_check( path ) ) ) {
    gam_fs_mon_type type;
    type = gam_fs_get_mon_type( path );
    if( type != GFS_MT_POLL ) {
      gam_subscription_free( sub );
    }
  }
  #endif
  g_free( path );
}

/**
   gam_listener_free:

   @listener: the #GamListener to free

   Frees a #GamListener returned by #gam_listener_new
*/
void
gam_listener_free( GamListener *listener ) {
  GList *cur;
  g_assert( listener );
  while( ( cur = g_list_first( listener->subs ) ) != NULL ) {
    GamSubscription * sub = cur->data;
    gam_listener_free_subscription( listener, sub );
    listener->subs = g_list_delete_link( listener->subs, cur );
  }
  g_free( listener->pidname );
  g_free( listener );
}

/**
   gam_listener_get_service:

   @listener: the #GamListener

   Gets the service associated with a #GamListener

   Returns the service associated with the #GamListener.  The result
   is owned by the #GamListener and must not be freed by the caller.
*/
void *
gam_listener_get_service( GamListener *listener ) {
  return listener->service;
}

/**
   gam_listener_get_pid:

   @listener: the #GamListener

   Gets the unique process ID associated with a #GamListener

   Returns the pid associated with the #GamListener.
*/
int
gam_listener_get_pid( GamListener *listener ) {
  return listener->pid;
}

/**
   gam_listener_get_pidname:

   @listener: the #GamListener

   Gets the process name associated with a #GamListener

   Returns the process name associated with the #GamListener.
*/
const char *
gam_listener_get_pidname( GamListener *listener ) {
  return listener->pidname;
}

/**
   gam_listener_get_subscription:

   @listener: the #GamListener
   @path: a path to a file or directory

   Gets the subscription to a path

   Returns the #GamSubscription to path, or NULL if there is none
*/
GamSubscription *
gam_listener_get_subscription( GamListener *listener, const char *path ) {
  GList *l;
  for( l = listener->subs; l; l = l->next ) {
    GamSubscription *sub = l->data;
    if( strcmp( gam_subscription_get_path( sub ), path ) == 0 ) {
      return sub;
    }
  }
  return NULL;
}

/**
   gam_listener_get_subscription_by_reqno:

   @listener: the #GamListener
   @reqno: a subscription request number

   Gets the subscription represented by the given reqno

   Returns a #GamSubscription, or NULL if it wasn't found
*/
GamSubscription *
gam_listener_get_subscription_by_reqno( GamListener * listener, int reqno ) {
  GList *l;
  for( l = listener->subs; l; l = l->next ) {
    GamSubscription *sub = l->data;
    if( gam_subscription_get_reqno( sub ) == reqno ) {
      return sub;
    }
  }
  return NULL;
}

/**
   gam_listener_is_subscribed:

   @listener: the #GamListener
   @path: the path to the file or directory

   Returns whether a #GamListener is subscribed to a file or directory

   Returns TRUE if listener has a subscription to the path, FALSE
   otherwise.
*/
gboolean
gam_listener_is_subscribed( GamListener *listener, const char *path ) {
  return gam_listener_get_subscription( listener, path ) != NULL;
}

/**
   gam_listener_add_subscription:

   @listener: the #GamListener
   @sub: the #GamSubscription to add

   Adds a subscription to the #GamListener
*/
void
gam_listener_add_subscription( GamListener *listener,
                               GamSubscription *sub ) {
  g_assert( listener );
  g_assert( sub );
  g_assert( !g_list_find( listener->subs, sub ) );
  listener->subs = g_list_prepend( listener->subs, sub );
  GAM_DEBUG( DEBUG_INFO, "Adding sub %s to listener %s\n", gam_subscription_get_path( sub ), listener->pidname );
}

/**
   gam_listener_remove_subscription:

   @listener: the #GamListener
   @sub: the #GamSubscription to remove

   Removes a subscription from the #GamListener.
*/
void
gam_listener_remove_subscription( GamListener *listener,
                                  GamSubscription *sub ) {
  g_assert( listener );
  g_assert( sub );
  g_assert( g_list_find( listener->subs, sub ) );
  listener->subs = g_list_remove( listener->subs, sub );
  GAM_DEBUG( DEBUG_INFO, "Removing sub %s from listener %s\n", gam_subscription_get_path( sub ), listener->pidname );
  /* There should only be one.  */
  g_assert( !g_list_find( listener->subs, sub ) );
}

/**
   gam_listener_get_subscriptions:

   @listener: the #GamListener

   Gets all the subscriptions a given listener has

   Returns a new list containing all of listener's subscriptions.  It
   is the responsibility of the caller to free the list.
*/
GList *
gam_listener_get_subscriptions( GamListener *listener ) {
  g_assert( listener );
  return g_list_copy( listener->subs );
}

/**
   gam_listener_debug:

   @listener: the #GamListener

   Print debugging information about a listener
*/
void
gam_listener_debug( GamListener *listener ) {
  #ifdef GAM_DEBUG_ENABLED
  GList *cur;
  if( listener == NULL ) {
    GAM_DEBUG( DEBUG_INFO, "  Listener is NULL\n" );
    return;
  }
  GAM_DEBUG( DEBUG_INFO, "  Listener %s has %d subscriptions registered\n", listener->pidname,
             g_list_length( listener->subs ) );
  for( cur = listener->subs; cur; cur = g_list_next( cur ) ) {
    gam_subscription_debug( ( GamSubscription * ) cur->data );
  }
  #endif
}

/** @} */
